﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using Pfz.Caching;

namespace Pfz.Threading.Unsafe
{
	/// <summary>
	/// Class that creates a thread to run Actions as messages. It only creates 
	/// one thread to process all messages. Use it only when you know you want to
	/// process many messages asynchronously, but don't want (or can't) use ThreadPool 
	/// threads.
	/// </summary>
	public sealed class UnsafeRunnableRunner:
		ThreadSafeDisposable
	{
		private ManagedAutoResetEvent _autoResetEvent = new ManagedAutoResetEvent();
		private Queue<IRunnable> _queue = new Queue<IRunnable>();
		private IRunnable _cleanup;
		
		/// <summary>
		/// Creates a new action runner.
		/// </summary>
		public UnsafeRunnableRunner(IRunnable cleanup)
		{
			_cleanup = cleanup;

			try
			{
				GCUtils.Collected += _Collected;
				UnlimitedThreadPool.Run(_Run);
			}
			catch
			{
				_autoResetEvent.Dispose();
				_autoResetEvent = null;

				Dispose();
				throw;
			}
		}
		
		/// <summary>
		/// Frees all used resources.
		/// </summary>
		[SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_autoResetEvent")]
		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				Disposer.Dispose(ref _autoResetEvent);

				GCUtils.Collected -= _Collected;
			}
			
			base.Dispose(disposing);
		}
		private void _Collected()
		{
			try
			{
				lock(DisposeLock)
				{
					if (WasDisposed)
					{
						GCUtils.Collected -= _Collected;
						return;
					}
						
					_queue = new Queue<IRunnable>(_queue);
				}
			}
			catch
			{
			}
		}

		/// <summary>
		/// Gets the number of pending items. Use this if you want to stablish a maximum.
		/// </summary>
		public int PendingCount
		{
			get
			{
				lock(DisposeLock)
				{
					CheckUndisposed();

					return _queue.Count;
				}
			}
		}
		
		private void _Run()
		{
			try
			{
				var autoResetEvent = _autoResetEvent;
				if (autoResetEvent == null)
					return;

				var thread = Thread.CurrentThread;

				while (true)
				{
					thread.IsBackground = true;
					autoResetEvent.WaitOne();
					thread.IsBackground = false;

					if (WasDisposed)
						return;

					while(true)
					{
						IRunnable runnable = null;
					
						lock(DisposeLock)
						{
							if (WasDisposed)
								return;

							var queue = _queue;
							if (queue.Count == 0)
								break;
								
							runnable = queue.Dequeue();
						}
					
						runnable.Run();
					}
				}
			}
			finally
			{
				if (_cleanup != null)
					_cleanup.Run();
			}
		}

		/// <summary>
		/// Enqueues and runs the given runnable asynchronously.
		/// </summary>
		/// <param name="runnable">The runnable to run.</param>
		public void Run(IRunnable runnable)
		{
			if (runnable == null)
				throw new ArgumentNullException("runnable");
				
			lock(DisposeLock)
			{
				CheckUndisposed();
					
				_queue.Enqueue(runnable);
			}
			
			_autoResetEvent.Set();
		}

		/// <summary>
		/// Enqueues and runs the given runnable asynchronously.
		/// </summary>
		/// <param name="runnable">The runnable to run.</param>
		/// <param name="value">The value to pass to the runnable.</param>
		public void Run<T>(IRunnable<T> runnable, T value)
		{
			if (runnable == null)
				throw new ArgumentNullException("runnable");
				
			var realRunnable = new Runnable<T>(runnable, value);
			Run(realRunnable);
		}
	}
}
